package sw

import (
	"bytes"
	"crypto/ecdsa"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"blockchain/bccsp"
)

// NewFileBasedKeyStore在给定的位置实例化一个基于文件的密钥库。
// 指定非空密码时，密钥库可以加密。
// 也可以设置为只读。在这种情况下，任何存储操作都将被禁止

func NewFileBasedKeyStore(pwd []byte, path string, readOnly bool) (bccsp.KeyStore, error) {
	ks := &fileBasedKeyStore{}
	return ks, ks.Init(pwd, path, readOnly)
}

// fileBasedKeyStore是一个基于文件夹的KeyStore。
// 每个密钥存储在一个单独的文件中，该文件的名称包含密钥的SKI和标识密钥类型的标志。
// 所有密钥都存储在一个文件夹中，该文件夹的路径在初始化时提供。
// KeyStore可以使用密码进行初始化，该密码用于加密和解密存储密钥的文件。
// 为了避免密钥覆盖，密钥库存储文件设置为只读。 600
// 存储文件名 为ski_标志服(sk/pk)
type fileBasedKeyStore struct {
	path string //存放密钥的路径

	readOnly bool //是否只读
	isOpen   bool //是否正在打开文件

	pwd []byte //加密密钥

	m sync.Mutex // 同步锁
}

// Init使用密码、存放密钥的文件夹路径和只读标志来初始化这个KeyStore。
// pwd:密码库密码
// path:存放密钥文件夹
// readOnly:是否只读
// 每个密钥存储在一个单独的文件中，该文件的名称包含密钥的SKI和标识密钥类型的标志。
// 如果KeyStore初始化了密码，则该密码用于加密和解密存储密钥的文件。
// 对于未加密的KeyStores, pwd可以为nil。
// 如果加密的密钥存储库在没有密码的情况下初始化，那么从密钥存储库中检索密钥将失败。
// 为了避免密钥覆盖，密钥库只能只读。
func (ks *fileBasedKeyStore) Init(pwd []byte, path string, readOnly bool) error {
	// 参数合法性校验
	if len(path) == 0 {
		return errors.New("an invalid KeyStore path provided. Path cannot be an empty string")
	}

	// 加锁
	ks.m.Lock()
	defer ks.m.Unlock()

	if ks.isOpen {
		return errors.New("keystore is already initialized")
	}

	ks.path = path

	clone := make([]byte, len(pwd))
	copy(clone, pwd)
	ks.pwd = clone
	ks.readOnly = readOnly

	//如果不存在文件夹创建文件夹
	exists, err := dirExists(path)
	if err != nil {
		return err
	}
	if !exists {
		err = ks.createKeyStore()
		if err != nil {
			return err
		}
		return ks.openKeyStore()
	}
	// 这里其实判断的也是dir为空
	empty, err := dirEmpty(path)
	if err != nil {
		return err
	}
	if empty {
		err = ks.createKeyStore()
		if err != nil {
			return err
		}
	}

	return ks.openKeyStore()
}

func (ks *fileBasedKeyStore) ReadOnly() bool {
	return ks.readOnly
}

//根据ski得到 key
func (ks *fileBasedKeyStore) GetKey(ski []byte) (bccsp.Key, error) {
	// 校验参数合法性
	if len(ski) == 0 {
		return nil, errors.New("invalid SKI. Cannot be of zero length")
	}
	// 得到key
	suffix := ks.getSuffix(hex.EncodeToString(ski))

	switch suffix {
	case "key":
		// 载入一个秘钥
		key, err := ks.loadKey(hex.EncodeToString(ski))
		if err != nil {
			return nil, fmt.Errorf("failed loading key [%x] [%s]", ski, err)
		}

		return &aesPrivateKey{key, false}, nil
	case "sk":
		//载入一个私钥
		key, err := ks.loadPrivateKey(hex.EncodeToString(ski))
		if err != nil {
			return nil, fmt.Errorf("failed loading secret key [%x] [%s]", ski, err)
		}

		switch k := key.(type) {
		case *ecdsa.PrivateKey:
			return &ecdsaPrivateKey{k}, nil
		default:
			return nil, errors.New("secret key type not recognized")
		}
	case "pk":
		// 载入一个公钥
		key, err := ks.loadPublicKey(hex.EncodeToString(ski))
		if err != nil {
			return nil, fmt.Errorf("failed loading public key [%x] [%s]", ski, err)
		}

		switch k := key.(type) {
		case *ecdsa.PublicKey:
			return &ecdsaPublicKey{k}, nil
		default:
			return nil, errors.New("public key type not recognized")
		}
	default:
		// 或者文件名被人篡改均会进入该分支
		return ks.searchKeystoreForSKI(ski)
	}
}

// StoreKey存储密钥k到KeyStore中。
//如果这个KeyStore是只读的，那么这个方法将失败。
func (ks *fileBasedKeyStore) StoreKey(k bccsp.Key) (err error) {
	if ks.readOnly {
		return errors.New("read only KeyStore")
	}

	if k == nil {
		return errors.New("invalid key. It must be different from nil")
	}
	//根据存储类型存储道各个前缀文件中
	switch kk := k.(type) {
	case *ecdsaPrivateKey:
		err = ks.storePrivateKey(hex.EncodeToString(k.SKI()), kk.privKey)
		if err != nil {
			return fmt.Errorf("failed storing ECDSA private key [%s]", err)
		}

	case *ecdsaPublicKey:
		err = ks.storePublicKey(hex.EncodeToString(k.SKI()), kk.pubKey)
		if err != nil {
			return fmt.Errorf("failed storing ECDSA public key [%s]", err)
		}

	case *aesPrivateKey:
		err = ks.storeKey(hex.EncodeToString(k.SKI()), kk.privKey)
		if err != nil {
			return fmt.Errorf("failed storing AES key [%s]", err)
		}

	default:
		return fmt.Errorf("key type not reconigned [%s]", k)
	}

	return
}

// 如果存储该key的无后缀会使用该方法去得到key
func (ks *fileBasedKeyStore) searchKeystoreForSKI(ski []byte) (k bccsp.Key, err error) {
	files, _ := ioutil.ReadDir(ks.path)
	for _, f := range files {
		if f.IsDir() {
			continue
		}

		// 64k，过大的文件肯定不是秘钥
		if f.Size() > (1 << 16) {
			continue
		}

		raw, err := ioutil.ReadFile(filepath.Join(ks.path, f.Name()))
		if err != nil {
			continue
		}

		key, err := pemToPrivateKey(raw, ks.pwd)
		if err != nil {
			continue
		}

		switch kk := key.(type) {
		case *ecdsa.PrivateKey:
			k = &ecdsaPrivateKey{kk}
		default:
			continue
		}

		if !bytes.Equal(k.SKI(), ski) {
			continue
		}

		return k, nil
	}
	return nil, fmt.Errorf("key with SKI %x not found in %s", ski, ks.path)
}

// 得到后缀
func (ks *fileBasedKeyStore) getSuffix(alias string) string {
	files, _ := ioutil.ReadDir(ks.path)
	for _, f := range files {
		// 需要所有的前缀是否符合,之后根据f.Name()去寻找后缀,判断私钥类型
		if strings.HasPrefix(f.Name(), alias) {
			// 是否拥有后缀  sk的是私钥
			if strings.HasSuffix(f.Name(), "sk") {
				return "sk"
			}
			// 是否拥有后缀 pk的是公钥
			if strings.HasSuffix(f.Name(), "pk") {
				return "pk"
			}
			// 是否拥有后缀 key,得到的是普通秘钥
			if strings.HasSuffix(f.Name(), "key") {
				return "key"
			}
			break
		}
	}
	// 否则返回无后缀
	return ""
}

//存储到私钥
func (ks *fileBasedKeyStore) storePrivateKey(alias string, privateKey interface{}) error {
	rawKey, err := privateKeyToPEM(privateKey, ks.pwd)
	if err != nil {
		logger.Errorf("Failed converting private key to PEM [%s]: [%s]", alias, err)
		return err
	}

	err = ioutil.WriteFile(ks.getPathForAlias(alias, "sk"), rawKey, 0o600)
	if err != nil {
		logger.Errorf("Failed storing private key [%s]: [%s]", alias, err)
		return err
	}

	return nil
}

//存储到一个公钥
func (ks *fileBasedKeyStore) storePublicKey(alias string, publicKey interface{}) error {
	rawKey, err := publicKeyToPEM(publicKey, ks.pwd)
	if err != nil {
		logger.Errorf("Failed converting public key to PEM [%s]: [%s]", alias, err)
		return err
	}

	err = ioutil.WriteFile(ks.getPathForAlias(alias, "pk"), rawKey, 0o600)
	if err != nil {
		logger.Errorf("Failed storing private key [%s]: [%s]", alias, err)
		return err
	}

	return nil
}

//存储到文件一个key
func (ks *fileBasedKeyStore) storeKey(alias string, key []byte) error {
	pem, err := aesToEncryptedPEM(key, ks.pwd)
	if err != nil {
		logger.Errorf("Failed converting key to PEM [%s]: [%s]", alias, err)
		return err
	}

	err = ioutil.WriteFile(ks.getPathForAlias(alias, "key"), pem, 0o600)
	if err != nil {
		logger.Errorf("Failed storing key [%s]: [%s]", alias, err)
		return err
	}

	return nil
}

// 导入私钥
func (ks *fileBasedKeyStore) loadPrivateKey(alias string) (interface{}, error) {
	path := ks.getPathForAlias(alias, "sk")
	logger.Debugf("Loading private key [%s] at [%s]...", alias, path)

	raw, err := ioutil.ReadFile(path)
	if err != nil {
		logger.Errorf("Failed loading private key [%s]: [%s].", alias, err.Error())

		return nil, err
	}

	privateKey, err := pemToPrivateKey(raw, ks.pwd)
	if err != nil {
		logger.Errorf("Failed parsing private key [%s]: [%s].", alias, err.Error())

		return nil, err
	}

	return privateKey, nil
}

// 导入公钥
func (ks *fileBasedKeyStore) loadPublicKey(alias string) (interface{}, error) {
	path := ks.getPathForAlias(alias, "pk")
	logger.Debugf("Loading public key [%s] at [%s]...", alias, path)

	raw, err := ioutil.ReadFile(path)
	if err != nil {
		logger.Errorf("Failed loading public key [%s]: [%s].", alias, err.Error())

		return nil, err
	}

	privateKey, err := pemToPublicKey(raw, ks.pwd)
	if err != nil {
		logger.Errorf("Failed parsing private key [%s]: [%s].", alias, err.Error())

		return nil, err
	}

	return privateKey, nil
}

// 导入key
func (ks *fileBasedKeyStore) loadKey(alias string) ([]byte, error) {
	path := ks.getPathForAlias(alias, "key")
	logger.Debugf("Loading key [%s] at [%s]...", alias, path)

	pem, err := ioutil.ReadFile(path)
	if err != nil {
		logger.Errorf("Failed loading key [%s]: [%s].", alias, err.Error())

		return nil, err
	}

	key, err := pemToAES(pem, ks.pwd)
	if err != nil {
		logger.Errorf("Failed parsing key [%s]: [%s]", alias, err)

		return nil, err
	}

	return key, nil
}

//创建key目录
func (ks *fileBasedKeyStore) createKeyStore() error {
	//如果keystore目录不存在，则创建它的根目录
	ksPath := ks.path
	logger.Debugf("Creating KeyStore at [%s]...", ksPath)
	//755的权限
	err := os.MkdirAll(ksPath, 0o755)
	if err != nil {
		return err
	}

	logger.Debugf("KeyStore created at [%s].", ksPath)
	return nil
}

//将ks的 isOpen置为打开
func (ks *fileBasedKeyStore) openKeyStore() error {
	if ks.isOpen {
		return nil
	}
	ks.isOpen = true
	logger.Debugf("KeyStore opened at [%s]...done", ks.path)

	return nil
}

// 设置的文件名为 ski_密钥标志符(pk/sk/key)
func (ks *fileBasedKeyStore) getPathForAlias(alias, suffix string) string {
	return filepath.Join(ks.path, alias+"_"+suffix)
}

//判断文件是否存在
func dirExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

//判断文件是否存在
func dirEmpty(path string) (bool, error) {
	f, err := os.Open(path)
	if err != nil {
		return false, err
	}
	defer f.Close()

	_, err = f.Readdir(1)
	if err == io.EOF {
		return true, nil
	}
	return false, err
}
